/*
 * The MIT License
 * 
 * Copyright (c) 2008 Comcast Interactive Media
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package cim.sf.core.command.internal;

import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;

import cim.sf.core.command.AnnotatedCommands;
import cim.sf.core.command.Command;
import cim.sf.core.command.CommandAvailabilityListener;
import cim.sf.core.command.CommandInvocation;
import cim.sf.core.command.CommandNotFoundException;
import cim.sf.core.command.CommandShell;

/**
 * 
 * @author <a href="mailto:jeremy_volkman@cable.comcast.com">Jeremy Volkman</a>
 * 
 */
public class CommandShellImpl implements BundleActivator, CommandShell {

    BundleContext context;
    private Map<AnnotatedCommands, AnnotatedCommandRegistrar> annotatedRegistrarMap;
    private Map<String, CommandBundlePair> currentCommands;
    private ServiceTracker commandTracker;
    private ServiceTracker annotatedCommandsTracker;

    public synchronized void start(BundleContext context) throws Exception {
        this.context = context;
        this.currentCommands = new ConcurrentHashMap<String, CommandBundlePair>();
        this.annotatedRegistrarMap = new IdentityHashMap<AnnotatedCommands, AnnotatedCommandRegistrar>();
        context.registerService(AnnotatedCommands.class.getName(), new TestCommands(), null);
        this.annotatedCommandsTracker = new ServiceTracker(context,
                AnnotatedCommands.class.getName(),
                new AnnotatedCommandsTracker());
        this.annotatedCommandsTracker.open();

        context.registerService(CommandShell.class.getName(), this, null);

        this.commandTracker = new ServiceTracker(context, Command.class
                .getName(), new CommandTracker());
        // All CommandAvailabilityListeners will be notified of commands being added
        this.commandTracker.open();
    }

    public synchronized void stop(BundleContext context) throws Exception {
        // All CommandAvailabilityListeners will be notified of commands being removed
        this.commandTracker.close();
        this.commandTracker = null;

        this.annotatedCommandsTracker.close();
        this.annotatedCommandsTracker = null;

        this.annotatedRegistrarMap = null;
        this.currentCommands = null;
        this.context = null;
    }

    private synchronized void addCommand(Command c, Bundle b) {
        String path = c.getPath();
        if (path != null && !currentCommands.containsKey(path)) {
            currentCommands.put(path, new CommandBundlePair(c, b));
            notifyListeners(path, false);
        }
    }

    private synchronized void removeCommand(Command c) {
        String path = c.getPath();
        if (path != null) {
            Command existingCommand = currentCommands.get(path).command;
            if (existingCommand == c) {
                currentCommands.remove(path);
                notifyListeners(path, true);
            }
        }
    }

    private void notifyListeners(String path, boolean removed) {
        try {
            ServiceReference[] refs = context.getServiceReferences(
                    CommandAvailabilityListener.class.getName(), null);
            if (refs != null) {
                for (ServiceReference ref : refs) {
                    CommandAvailabilityListener l = (CommandAvailabilityListener) context
                            .getService(ref);
                    try {
                        if (removed) {
                            l.commandRemoved(path);
                        } else {
                            l.commandAdded(path);
                        }
                    } finally {
                        context.ungetService(ref);
                    }
                }
            }
        } catch (InvalidSyntaxException e) {
            // won't happen
        }
    }

    public int executeCommand(String commandPath, CommandInvocation ci)
            throws CommandNotFoundException {
        CommandBundlePair cmd = currentCommands.get(commandPath);
        if (cmd == null) {
            throw new CommandNotFoundException(commandPath);
        }
        return CommandUtils.executeCommand(cmd.command, cmd.bundle, ci);
    }

    public Bundle getCommandBundle(String commandPath)
        throws CommandNotFoundException {
        CommandBundlePair cmd = currentCommands.get(commandPath);
        if (cmd == null) {
            throw new CommandNotFoundException(commandPath);
        }
        return cmd.bundle;
    }

    public String getCommandDescription(String commandPath)            
        throws CommandNotFoundException {
        CommandBundlePair cmd = currentCommands.get(commandPath);
        if (cmd == null) {
            throw new CommandNotFoundException(commandPath);
        }
        return cmd.command.getDescription();
    }

    public String[] getCommandUsage(String commandPath)
        throws CommandNotFoundException {
        CommandBundlePair cmd = currentCommands.get(commandPath);
        if (cmd == null) {
            throw new CommandNotFoundException(commandPath);
        }
        return cmd.command.getUsage();
    }

    public String[] getCommands() {
        String[] cmds = currentCommands.keySet().toArray(new String[currentCommands.size()]);
        Arrays.sort(cmds);
        return cmds;
    }

    private class AnnotatedCommandsTracker implements ServiceTrackerCustomizer {

        public synchronized Object addingService(ServiceReference ref) {
            BundleContext ctx = ref.getBundle().getBundleContext();
            if (ctx == null) {
                ctx = context;
            }
            AnnotatedCommands provider = (AnnotatedCommands) ctx
                    .getService(ref);
            if (provider == null) {
                return null;
            }
            AnnotatedCommandRegistrar registrar = new AnnotatedCommandRegistrar(
                    provider);
            registrar.open(ctx);
            annotatedRegistrarMap.put(provider, registrar);
            return provider;
        }

        public void modifiedService(ServiceReference ref, Object obj) {
            // Do nothing
        }

        public synchronized void removedService(ServiceReference ref, Object obj) {
            if (annotatedRegistrarMap == null)
                return;
            AnnotatedCommandRegistrar registrar = annotatedRegistrarMap
                    .remove(obj);
            if (registrar != null) {
                registrar.close();
            }
        }
    }

    private class CommandTracker implements ServiceTrackerCustomizer {
        public synchronized Object addingService(ServiceReference ref) {
            Command c = (Command) context.getService(ref);
            addCommand(c, ref.getBundle());
            return c;
        }

        public void modifiedService(ServiceReference ref, Object obj) {
        }

        public synchronized void removedService(ServiceReference ref, Object obj) {
            Command removedCommand = (Command) obj;
            removeCommand(removedCommand);
        }
    }

    private static class CommandBundlePair {
        Command command;
        Bundle bundle;

        CommandBundlePair(Command c, Bundle b) {
            this.command = c;
            this.bundle = b;
        }
    }

}
